package org.jugile.util;

/*

Copyright (C) 2007-2011 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;

/**
 * "Blessed be Jahwe the God of Israel, from eternity and to eternity!" (1. Chronicles. 16:36)
 * From Eternity to Eternity - there is an arc - Time. 
 * 
 * ==========
 * 
 * Simpler wrapper for java time handling objects. 
 * Currently uses date as internal representation but will be replaced with Calendar if needed.
 * @author jukka.rahkonen@iki.fi
 *
 */
public class Time extends Jugile implements Serializable, Comparable<Time> {
	private static final long serialVersionUID = 1L;
	
	private final Date date; // immutable
	
	public Time(String str) {
		if (empty(str)) {
			date = new Date(0L);
		} else if ("0".equals(str)) {
			date = new Date(0L);
		} else {
			long ts = Jugile.parseLongSafe(str);
			if (ts > 0) date = new Date(ts);
			else date = U.sdf(str);
		}
	}
	public Time(String str, String fmt) {
		date = U.sdf(str, fmt);
	}
	public Time(String str, String fmt, Locale loc) {
		Date d = null;
		try {
			SimpleDateFormat sdf = new SimpleDateFormat(fmt,loc);
			d = sdf.parse(str);
		} catch (Exception e) {	fail(e); }
		date = d;
	}
	
	public Time(int year, int month, int day, int hour, int min, int sec) {
		date = getD(year, month, day, hour, min, sec);
	}
	public Time(int year, int month, int day) {
		date = getD(year, month, day, 0, 0, 0);
	}
	public Time(Date d) {
		date = new Date(d.getTime());
	}
	public Time() {
		date = now().date;
	}
	public Time(long ms) {
		date = new Date(ms);
	}
	public Time(int year, int weeknum) {
		Time t = new Time(year, 1, 1);
		int wdayn = t.getWeekDayNum(); // ma = 0
		if (wdayn > 0) t = t.plus(24*(7-wdayn));
		t = t.plus(24*7*(weeknum-1));
		date = t.date;
	}

	public String toString() {
		if (date == null) return "";
		return U.sdf(date);
	}	
	
	public String toString(String fmt) {
		if (date == null) return "";
		return U.sdf(date, fmt);
	}

	public String toString(String fmt, Locale loc) {
		try {
			SimpleDateFormat sdf = new SimpleDateFormat(fmt,loc);
			return sdf.format(date);
		} catch (Exception e) { fail(e); } 
		return null;
	}

	public String toSql() {
		return "'"+U.sdf(date, "yyyy-MM-dd HH:mm:ss")+"'";
	}
	
	//public String fmt(String fmt) { return String.format(fmt, date); }
	
	public long getMs() { return date.getTime(); }
	public Date getDate() { return new Date(date.getTime()); }

	public Time plus(Duration d) {
		return new Time(date.getTime() + d.getMs());
	}
	public Time plus(String str) { return plus(new Duration(str)); }
	public Time plus(int hours) { return plus(new Duration(hours)); }

	public Time minus(Duration d) {
		return new Time(date.getTime() - d.getMs());
	}
	public Time minus(String str) { return minus(new Duration(str)); }
	public Time minus(int hours) { return minus(new Duration(hours)); }
	public Duration minus(Time t) {
		if (t == null) return new Duration(0);
		long ms = getMs() - t.getMs();
		if (ms < 0) ms = -ms;
		return new Duration(ms);
	}
	
	private Date getD(int year, int month, int day, int hour, int min, int sec) {
		if (month < 1 || month > 12) fail("invalid month: " + month);
		if (day < 1 || day > 31) fail("invalid day: " + day);
		if (hour < 0 || hour > 23) fail("invalid hour: " + hour);
		if (min < 0 || min > 59) fail("invalid minute: " + min);
		if (sec < 0 || sec > 59) fail("invalid second: " + sec);
		Calendar c = new GregorianCalendar();
		c.set(year, month-1, day, hour, min, sec);
		c.set(Calendar.MILLISECOND,0);
		return c.getTime();
	}
	
	public boolean equals(Time t) {
		if (t == null) return false;
		return (compareTo(t) == 0);
	}

	public boolean equals(Object o) {
		if (!(o instanceof Time)) return false;
		return equals((Time)o);
	}
	
	public int hashCode() {
		if (date == null) return 0;
		return date.hashCode();
	}
	
	public boolean gt(Time t) { return (compareTo(t) > 0); }
	public boolean lt(Time t) { return (compareTo(t) < 0); }
	public boolean gte(Time t) { return (gt(t) || equals(t)); }
	public boolean lte(Time t) { return (lt(t) || equals(t)); }
	public boolean eq(Time t) { return equals(t); }
	
	public boolean after(Time t) { return gt(t); }
	public boolean before(Time t) { return lt(t); }
	
	public boolean isToday() { return isIn24h(now().getDayStart()); }
	public boolean isYesterday() { return isIn24h(now().getDayStart().minus(24)); }
	public boolean isTomorrow() { return isIn24h(now().getDayStart().plus(24)); }

	public boolean isBetween(Time start, Time end) { return gte(start) && lt(end); }
	private boolean isIn24h(Time start) { return isBetween(start,start.plus(24)); }
		
	// --- Comparable interface ---
	public int compareTo(Time o) {
		if (date == null && o == null) return 0; // TODO: check this logic
		if (date == null) return -1;
		Long ts = new Long(date.getTime());
		if (o == null) return ts.compareTo(new Long(0));
		return ts.compareTo(new Long(o.getMs()));
	}

	
	public String getWday() { return getWday(new Locale("fi"));	}
	public String getWday(Locale loc) {
		SimpleDateFormat sdf = new SimpleDateFormat("E", loc);
		return sdf.format(date);
	}
	
	public Time getDayStart() {
		return new Time(U.sdf(date)); 
	}
	public Period getDay() {
		Time start = getDayStart();
		Time end = start.plus(24); // TODO: should it be 1 ms before midnight ?
		return new Period(start,end);
	}
	
	public Time getWeekStart() {
		Time start = getDayStart().plus(12);
		for (int i = 0; i < 7; i++) {
			if (eq("ma",start.getWday())) return start;
			start = start.minus(24);
		}
		return null;
	}
	
	public Time getMonthStart() { return getMonthPeriod().getStart(); }
	public Time getNextMonthStart() { return getMonthPeriod().getEnd(); }
	public Period getMonthPeriod() {
		int month = parseIntSafe(toString("M"));
		int year = parseIntSafe(toString("yyyy"));
		int nextyear = year;
		int nextmonth = month+1;
		if (nextmonth > 12) {
			nextmonth = 1;
			nextyear++;
		}
		Time start = new Time("1."+month+"."+year);
		Time end = new Time("1."+nextmonth+"."+nextyear);
		return new Period(start,end);
	}
	
	
	public String getWeekNum() {
		return toString("w");
	}
	public int getWeekDayNum() {
		int d = date.getDay();
		if (d == 0) return 6; // sunday
		return d -1;
	}

	
	/**
	 * @return Time in W3C format without timezone.
	 */
	public String getW3c() {
		return toString("yyyy-MM-dd'T'HH:mm:ss");
	}
	
	/**
	 * @return Time in W3C format with timezone.
	 */
	public String getW3cZ() {
		return toString("yyyy-MM-dd'T'HH:mm:ssZ");
	}
	
	public static Time parseW3c(String str) {
		Date d = U.sdfSafe(str, "yyyy-MM-dd'T'HH:mm:ssZ");
		if (d == null) d = U.sdfSafe(str, "yyyy-MM-dd'T'HH:mm:ss");
		if (d == null) d = U.sdfSafe(str, "yyyy-MM-dd'T'HH:mm");
		if (d == null) d = U.sdfSafe(str, "yyyy-MM-dd");
		if (d == null) return null;
		return new Time(d);
	}
	
	public String getUiTs() {
		return toString("yyyy-MM-dd HH:mm:ss.SSS");
	}
	
	public String getJson() { return toJson(); }
	public String toJson() {
		return toString("yyyy/MM/dd HH:mm:ss"); // TODO: real timezone
	}	
	public static Time parseJson(String str) {
		Date d = U.sdfSafe(str, "yyyy/MM/dd HH:mm:ss");
		if (d == null) d = U.sdfSafe(str, "yyyy/MM/dd HH:mm");
		if (d == null) d = U.sdfSafe(str, "yyyy/MM/dd");
		if (d == null) return null;
		return new Time(d);
	}
	
	public Time getTvDayStart() {
		Time t = getDayStart();
		if (date.getHours() < 4) t = t.minus(20);
		else t = t.plus(4);
		return t;
	}
	
}
